package com.ternnetwork.batch.oxm.xstream;

import com.thoughtworks.xstream.MarshallingStrategy;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.*;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.core.DefaultConverterLookup;
import com.thoughtworks.xstream.core.util.CompositeClassLoader;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.StreamException;
import com.thoughtworks.xstream.io.naming.NameCoder;
import com.thoughtworks.xstream.io.xml.*;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.oxm.MarshallingFailureException;
import org.springframework.oxm.UncategorizedMappingException;
import org.springframework.oxm.UnmarshallingFailureException;
import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.support.AbstractMarshaller;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.StaxUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;

import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by xuwenfeng on 16-8-8.
 * new XmlFriendlyNameCoder("_-", "_");
 * 实现属性名的默认的双下划线变成单下划线
 *
 */
public class ExtendedXStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
    public static final String DEFAULT_ENCODING = "UTF-8";
    private ReflectionProvider reflectionProvider;
    private HierarchicalStreamDriver streamDriver;
    private HierarchicalStreamDriver defaultDriver;
    private Mapper mapper;
    private Class<?>[] mapperWrappers;
    private ConverterLookup converterLookup = new DefaultConverterLookup();
    private ConverterRegistry converterRegistry;
    private ConverterMatcher[] converters;
    private MarshallingStrategy marshallingStrategy;
    private Integer mode;
    private Map<String, ?> aliases;
    private Map<String, ?> aliasesByType;
    private Map<String, String> fieldAliases;
    private Class<?>[] useAttributeForTypes;
    private Map<?, ?> useAttributeFor;
    private Map<Class<?>, String> implicitCollections;
    private Map<Class<?>, String> omittedFields;
    private Class<?>[] annotatedClasses;
    private boolean autodetectAnnotations;
    private String encoding;
    private NameCoder nameCoder;
    private Class<?>[] supportedClasses;
    private ClassLoader beanClassLoader;
    private XStream xstream;

    public ExtendedXStreamMarshaller() {
        this.converterRegistry = (ConverterRegistry)this.converterLookup;
        this.encoding = "UTF-8";
        this.nameCoder = new XmlFriendlyNameCoder("_-", "_");
        this.beanClassLoader = new CompositeClassLoader();
    }

    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
        this.reflectionProvider = reflectionProvider;
    }

    public void setStreamDriver(HierarchicalStreamDriver streamDriver) {
        this.streamDriver = streamDriver;
        this.defaultDriver = streamDriver;
    }

    private HierarchicalStreamDriver getDefaultDriver() {
        if(this.defaultDriver == null) {
            this.defaultDriver = new XppDriver();
        }

        return this.defaultDriver;
    }

    public void setMapper(Mapper mapper) {
        this.mapper = mapper;
    }

    public void setMapperWrappers(Class... mapperWrappers) {
        this.mapperWrappers = mapperWrappers;
    }

    public void setConverterLookup(ConverterLookup converterLookup) {
        this.converterLookup = converterLookup;
        if(converterLookup instanceof ConverterRegistry) {
            this.converterRegistry = (ConverterRegistry)converterLookup;
        }

    }

    public void setConverterRegistry(ConverterRegistry converterRegistry) {
        this.converterRegistry = converterRegistry;
    }

    public void setConverters(ConverterMatcher... converters) {
        this.converters = converters;
    }

    public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) {
        this.marshallingStrategy = marshallingStrategy;
    }

    public void setMode(int mode) {
        this.mode = Integer.valueOf(mode);
    }

    public void setAliases(Map<String, ?> aliases) {
        this.aliases = aliases;
    }

    public void setAliasesByType(Map<String, ?> aliasesByType) {
        this.aliasesByType = aliasesByType;
    }

    public void setFieldAliases(Map<String, String> fieldAliases) {
        this.fieldAliases = fieldAliases;
    }

    public void setUseAttributeForTypes(Class... useAttributeForTypes) {
        this.useAttributeForTypes = useAttributeForTypes;
    }

    public void setUseAttributeFor(Map<?, ?> useAttributeFor) {
        this.useAttributeFor = useAttributeFor;
    }

    public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
        this.implicitCollections = implicitCollections;
    }

    public void setOmittedFields(Map<Class<?>, String> omittedFields) {
        this.omittedFields = omittedFields;
    }

    public void setAnnotatedClasses(Class... annotatedClasses) {
        this.annotatedClasses = annotatedClasses;
    }

    public void setAutodetectAnnotations(boolean autodetectAnnotations) {
        this.autodetectAnnotations = autodetectAnnotations;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    protected String getDefaultEncoding() {
        return this.encoding;
    }

    public void setNameCoder(NameCoder nameCoder) {
        this.nameCoder = nameCoder;
    }

    public void setSupportedClasses(Class... supportedClasses) {
        this.supportedClasses = supportedClasses;
    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    public void afterPropertiesSet() {
        this.xstream = this.buildXStream();
    }

    protected XStream buildXStream() {
        XStream xstream = this.constructXStream();
        this.configureXStream(xstream);
        this.customizeXStream(xstream);
        return xstream;
    }

    protected XStream constructXStream() {
        return new XStream(this.reflectionProvider, this.getDefaultDriver(), this.beanClassLoader, this.mapper, this.converterLookup, this.converterRegistry) {
            protected MapperWrapper wrapMapper(MapperWrapper next) {
                MapperWrapper mapperToWrap = next;
                if(ExtendedXStreamMarshaller.this.mapperWrappers != null) {
                    Class[] var3 = ExtendedXStreamMarshaller.this.mapperWrappers;
                    int var4 = var3.length;

                    for(int var5 = 0; var5 < var4; ++var5) {
                        Class mapperWrapper = var3[var5];
                        Assert.isAssignable(MapperWrapper.class, mapperWrapper);

                        Constructor ctor;
                        try {
                            ctor = mapperWrapper.getConstructor(new Class[]{Mapper.class});
                        } catch (NoSuchMethodException var12) {
                            try {
                                ctor = mapperWrapper.getConstructor(new Class[]{MapperWrapper.class});
                            } catch (NoSuchMethodException var11) {
                                throw new IllegalStateException("No appropriate MapperWrapper constructor found: " + mapperWrapper);
                            }
                        }

                        try {
                            mapperToWrap = (MapperWrapper)ctor.newInstance(new Object[]{mapperToWrap});
                        } catch (Exception var10) {
                            throw new IllegalStateException("Failed to construct MapperWrapper: " + mapperWrapper);
                        }
                    }
                }

                return mapperToWrap;
            }
        };
    }

    protected void configureXStream(XStream xstream) {
        if(this.converters != null) {
            for(int ex = 0; ex < this.converters.length; ++ex) {
                if(this.converters[ex] instanceof Converter) {
                    xstream.registerConverter((Converter)this.converters[ex], ex);
                } else {
                    if(!(this.converters[ex] instanceof SingleValueConverter)) {
                        throw new IllegalArgumentException("Invalid ConverterMatcher [" + this.converters[ex] + "]");
                    }

                    xstream.registerConverter((SingleValueConverter)this.converters[ex], ex);
                }
            }
        }

        if(this.marshallingStrategy != null) {
            xstream.setMarshallingStrategy(this.marshallingStrategy);
        }

        if(this.mode != null) {
            xstream.setMode(this.mode.intValue());
        }

        int idx;
        Iterator var12;
        Map.Entry var14;
        try {
            Iterator entry;
            Map.Entry fields;
            Map var11;
            if(this.aliases != null) {
                var11 = this.toClassMap(this.aliases);
                entry = var11.entrySet().iterator();

                while(entry.hasNext()) {
                    fields = (Map.Entry)entry.next();
                    xstream.alias((String)fields.getKey(), (Class)fields.getValue());
                }
            }

            if(this.aliasesByType != null) {
                var11 = this.toClassMap(this.aliasesByType);
                entry = var11.entrySet().iterator();

                while(entry.hasNext()) {
                    fields = (Map.Entry)entry.next();
                    xstream.aliasType((String)fields.getKey(), (Class)fields.getValue());
                }
            }

            if(this.fieldAliases != null) {
                var12 = this.fieldAliases.entrySet().iterator();

                while(var12.hasNext()) {
                    var14 = (Map.Entry)var12.next();
                    String var16 = (String)var14.getValue();
                    String listValue = (String)var14.getKey();
                    idx = listValue.lastIndexOf(46);
                    if(idx == -1) {
                        throw new IllegalArgumentException("Field name [" + listValue + "] does not contain \'.\'");
                    }

                    String element = listValue.substring(0, idx);
                    Class field = ClassUtils.forName(element, this.beanClassLoader);
                    String fieldName = listValue.substring(idx + 1);
                    xstream.aliasField(var16, field, fieldName);
                }
            }
        } catch (ClassNotFoundException var10) {
            throw new IllegalStateException("Failed to load specified alias class", var10);
        }

        if(this.useAttributeForTypes != null) {
            Class[] var13 = this.useAttributeForTypes;
            int var15 = var13.length;

            for(int var17 = 0; var17 < var15; ++var17) {
                Class var18 = var13[var17];
                xstream.useAttributeFor(var18);
            }
        }

        if(this.useAttributeFor != null) {
            var12 = this.useAttributeFor.entrySet().iterator();

            label136:
            while(true) {
                while(true) {
                    if(!var12.hasNext()) {
                        break label136;
                    }

                    var14 = (Map.Entry)var12.next();
                    if(var14.getKey() instanceof String) {
                        if(!(var14.getValue() instanceof Class)) {
                            throw new IllegalArgumentException("\'useAttributesFor\' takes Map<String, Class> when using a map key of type String");
                        }

                        xstream.useAttributeFor((String)var14.getKey(), (Class)var14.getValue());
                    } else {
                        if(!(var14.getKey() instanceof Class)) {
                            throw new IllegalArgumentException("\'useAttributesFor\' property takes either a map key of type String or Class");
                        }

                        Class var19 = (Class)var14.getKey();
                        if(var14.getValue() instanceof String) {
                            xstream.useAttributeFor(var19, (String)var14.getValue());
                        } else {
                            if(!(var14.getValue() instanceof List)) {
                                throw new IllegalArgumentException("\'useAttributesFor\' property takes either Map<Class, String> or Map<Class, List<String>> when using a map key of type Class");
                            }

                            List var20 = (List)var14.getValue();
                            Iterator var23 = var20.iterator();

                            while(var23.hasNext()) {
                                Object var24 = var23.next();
                                if(var24 instanceof String) {
                                    xstream.useAttributeFor(var19, (String)var24);
                                }
                            }
                        }
                    }
                }
            }
        }

        String[] var21;
        String[] var22;
        int var25;
        String var26;
        if(this.implicitCollections != null) {
            var12 = this.implicitCollections.entrySet().iterator();

            while(var12.hasNext()) {
                var14 = (Map.Entry)var12.next();
                var21 = StringUtils.commaDelimitedListToStringArray((String)var14.getValue());
                var22 = var21;
                idx = var21.length;

                for(var25 = 0; var25 < idx; ++var25) {
                    var26 = var22[var25];
                    xstream.addImplicitCollection((Class)var14.getKey(), var26);
                }
            }
        }

        if(this.omittedFields != null) {
            var12 = this.omittedFields.entrySet().iterator();

            while(var12.hasNext()) {
                var14 = (Map.Entry)var12.next();
                var21 = StringUtils.commaDelimitedListToStringArray((String)var14.getValue());
                var22 = var21;
                idx = var21.length;

                for(var25 = 0; var25 < idx; ++var25) {
                    var26 = var22[var25];
                    xstream.omitField((Class)var14.getKey(), var26);
                }
            }
        }

        if(this.annotatedClasses != null) {
            xstream.processAnnotations(this.annotatedClasses);
        }

        if(this.autodetectAnnotations) {
            xstream.autodetectAnnotations(true);
        }

    }

    private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoundException {
        LinkedHashMap result = new LinkedHashMap(map.size());

        String key;
        Class type;
        for(Iterator var3 = map.entrySet().iterator(); var3.hasNext(); result.put(key, type)) {
            Map.Entry entry = (Map.Entry)var3.next();
            key = (String)entry.getKey();
            Object value = entry.getValue();
            if(value instanceof Class) {
                type = (Class)value;
            } else {
                if(!(value instanceof String)) {
                    throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class");
                }

                String className = (String)value;
                type = ClassUtils.forName(className, this.beanClassLoader);
            }
        }

        return result;
    }

    protected void customizeXStream(XStream xstream) {
    }

    public final XStream getXStream() {
        if(this.xstream == null) {
            this.xstream = this.buildXStream();
        }

        return this.xstream;
    }

    public boolean supports(Class<?> clazz) {
        if(ObjectUtils.isEmpty(this.supportedClasses)) {
            return true;
        } else {
            Class[] var2 = this.supportedClasses;
            int var3 = var2.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Class supportedClass = var2[var4];
                if(supportedClass.isAssignableFrom(clazz)) {
                    return true;
                }
            }

            return false;
        }
    }

    protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
        DomWriter streamWriter;
        if(node instanceof Document) {
            streamWriter = new DomWriter((Document)node, this.nameCoder);
        } else {
            if(!(node instanceof Element)) {
                throw new IllegalArgumentException("DOMResult contains neither Document nor Element");
            }

            streamWriter = new DomWriter((Element)node, node.getOwnerDocument(), this.nameCoder);
        }

        this.doMarshal(graph, streamWriter, (DataHolder)null);
    }

    protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException {
        ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter);
        LexicalHandler lexicalHandler = null;
        if(contentHandler instanceof LexicalHandler) {
            lexicalHandler = (LexicalHandler)contentHandler;
        }

        this.marshalSaxHandlers(graph, contentHandler, lexicalHandler);
    }

    protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
        try {
            this.doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), (DataHolder)null);
        } catch (XMLStreamException var4) {
            throw this.convertXStreamException(var4, true);
        }
    }

    protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) throws XmlMappingException {
        SaxWriter saxWriter = new SaxWriter(this.nameCoder);
        saxWriter.setContentHandler(contentHandler);
        this.doMarshal(graph, saxWriter, (DataHolder)null);
    }

    public void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException {
        this.marshalOutputStream(graph, outputStream, (DataHolder)null);
    }

    public void marshalOutputStream(Object graph, OutputStream outputStream, DataHolder dataHolder) throws XmlMappingException, IOException {
        if(this.streamDriver != null) {
            this.doMarshal(graph, this.streamDriver.createWriter(outputStream), dataHolder);
        } else {
            this.marshalWriter(graph, new OutputStreamWriter(outputStream, this.encoding), dataHolder);
        }

    }

    public void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
        this.marshalWriter(graph, writer, (DataHolder)null);
    }

    public void marshalWriter(Object graph, Writer writer, DataHolder dataHolder) throws XmlMappingException, IOException {
        if(this.streamDriver != null) {
            this.doMarshal(graph, this.streamDriver.createWriter(writer), dataHolder);
        } else {
            this.doMarshal(graph, new CompactWriter(writer), dataHolder);
        }

    }

    private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, DataHolder dataHolder) {
        try {
            this.getXStream().marshal(graph, streamWriter, dataHolder);
        } catch (Exception var12) {
            throw this.convertXStreamException(var12, true);
        } finally {
            try {
                streamWriter.flush();
            } catch (Exception var11) {
                this.logger.debug("Could not flush HierarchicalStreamWriter", var11);
            }

        }

    }

    protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException {
        if(streamSource.getInputStream() != null) {
            return this.unmarshalInputStream(streamSource.getInputStream());
        } else if(streamSource.getReader() != null) {
            return this.unmarshalReader(streamSource.getReader());
        } else {
            throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader");
        }
    }

    protected Object unmarshalDomNode(Node node) throws XmlMappingException {
        DomReader streamReader;
        if(node instanceof Document) {
            streamReader = new DomReader((Document)node, this.nameCoder);
        } else {
            if(!(node instanceof Element)) {
                throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
            }

            streamReader = new DomReader((Element)node, this.nameCoder);
        }

        return this.doUnmarshal(streamReader, (DataHolder)null);
    }

    protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException {
        try {
            XMLStreamReader ex = StaxUtils.createEventStreamReader(eventReader);
            return this.unmarshalXmlStreamReader(ex);
        } catch (XMLStreamException var3) {
            throw this.convertXStreamException(var3, false);
        }
    }

    protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException {
        return this.doUnmarshal(new StaxReader(new QNameMap(), streamReader, this.nameCoder), (DataHolder)null);
    }

    protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) throws XmlMappingException, IOException {
        throw new UnsupportedOperationException("XStreamMarshaller does not support unmarshalling using SAX XMLReaders");
    }

    public Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
        return this.unmarshalInputStream(inputStream, (DataHolder)null);
    }

    public Object unmarshalInputStream(InputStream inputStream, DataHolder dataHolder) throws XmlMappingException, IOException {
        return this.streamDriver != null?this.doUnmarshal(this.streamDriver.createReader(inputStream), dataHolder):this.unmarshalReader(new InputStreamReader(inputStream, this.encoding), dataHolder);
    }

    public Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
        return this.unmarshalReader(reader, (DataHolder)null);
    }

    public Object unmarshalReader(Reader reader, DataHolder dataHolder) throws XmlMappingException, IOException {
        return this.doUnmarshal(this.getDefaultDriver().createReader(reader), dataHolder);
    }

    private Object doUnmarshal(HierarchicalStreamReader streamReader, DataHolder dataHolder) {
        try {
            return this.getXStream().unmarshal(streamReader, (Object)null, dataHolder);
        } catch (Exception var4) {
            throw this.convertXStreamException(var4, false);
        }
    }

    protected XmlMappingException convertXStreamException(Exception ex, boolean marshalling) {
        return (XmlMappingException)(!(ex instanceof StreamException) && !(ex instanceof CannotResolveClassException) && !(ex instanceof ConversionException)?new UncategorizedMappingException("Unknown XStream exception", ex):(marshalling?new MarshallingFailureException("XStream marshalling exception", ex):new UnmarshallingFailureException("XStream unmarshalling exception", ex)));
    }
}
